# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Check modifications to gvn:* revprops.

- Only allow users to Add gvn:approve:theirusername
- Only allow users to Add gvn:submitted if valid
- Block Add of any other gvn:* revprops
- Block Modification and Delete of all gvn:* revprops, including
  gvn:approve:*, gvn:change, and gvn:submitted.
"""

import os

import svn.core
import svn.fs

import gvn.util


def CheckSubmitted(hi):
  """This takes some explaining: when gvn submits a revision 100, it
  sets a gvn:change property (at commit time, i.e. not through this
  hook) and points it to the snapshot the change came from (let's say
  rev 90).  gvn:change looks like this: user/branchname@90.  Then, it
  goes in rev 90 and sets property gvn:submitted to 100 (which is
  hi.propvalue here).
  """
  try:
    submitted_rev = int(hi.propvalue)
  except ValueError:
    return ("Error: %s tried to set revprop %s to invalid %s" %
            (hi.user, hi.propname, hi.propvalue))

  # gvn:submitted must be a revision number greater than what it's
  # being set on (since the submit comes after the snapshot) but <=
  # the head revision (i.e. must exist).
  if not hi.revision < submitted_rev <= hi.head:
    return ("%d is an invalid revision number for gvn:submitted"
            % (submitted_rev,))

  # Now, we check if the gvn:submitted value it pointing to a proper
  # revision and we get its gvn:change property.
  gvn_change = svn.fs.revision_prop(hi.fs, submitted_rev, "gvn:change",
                                    hi.pool)
  if gvn_change is None:
    return ("Cannot set gvn:submitted to r%d, which has no gvn:change"
            % (submitted_rev,))

  # then, we get the revision gvn:change points back to
  (username, branchname, revision) = gvn.util.ParseChangeName(gvn_change)
  if None in [username, branchname, revision]:
    return ("Cannot set gvn:submitted to r%d, whose gvn:change (%s) is invalid"
            % (submitted_rev, gvn_change))

  # at this point the rev from gvn:change should be the current revision we're
  # adding the gvn:submitted property to
  if revision != hi.revision:
    return ("""\
Cannot set gvn:submitted to r%d, whose gvn:change (%s) does not point to %d"""
            % (submitted_rev, gvn_change, hi.revision))

  if username != hi.user:
    return ("Cannot set gvn:submitted on %s's change (%s)"
            % (username, gvn_change))

  # TODO(epg): Verify that changebranch actually exists?

  # Looks good.
  return None

def RunHook(hi, logger):
  """Implements gvn.hooks.runner's RunHook interface

  Returns:
    -1: pass and bypass other hooks
    0: pass
    1: fail
    "string": fail and print string to the user
  """

  if not hi.propname.startswith('gvn:'):
    # then this hook doesn't care and passes
    return 0

  # Allow adding (but not removing) an approve property in your name.
  if hi.propname == "gvn:approve:" + hi.user and hi.action == "A":
    logger.info("%s set gvn:approve:%s on revision %s" %
                                                (hi.user, hi.user, hi.revision))
    return 0

  # Allow setting gvn:submitted to a revision whose gvn:change
  # property corresponds to this revision (hi.revision).
  if hi.propname == 'gvn:submitted':
    # gvn:submitted can only be added, not removed or modified
    if hi.action != 'A':
      return ("Error: user %s is not allowed to %s property "
                "gvn:submitted from revision %s" %
                 (hi.user, hi.action_word, hi.revision))

    retstr = CheckSubmitted(hi)
    if retstr is not None:
      # gvn:submitted is somehow invalid; log and return.
      return retstr

    # all is well, things match
    return 0

  # By here, we're trying to change some other gvn:xxx, denied.
  return ("%s cannot %s %s on revision %s" %
            (hi.user, hi.action_word,  hi.propname, hi.revision))
